#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <asm/string.h>
#include <asm/uaccess.h>
 
#define MAX_LED	12
 
/* Start: Registers */
// For chip's enable
#define	REG_CHIP_ENABLE	0x00
 
// For pwm's value
#define REG_PWM_LED1	0x04
#define REG_PWM_LED2	0x05
#define REG_PWM_LED3	0x06
#define REG_PWM_LED4	0x07
#define REG_PWM_LED5	0x08
#define REG_PWM_LED6	0x09
#define REG_PWM_LED7	0x0A
#define REG_PWM_LED8	0x0B
#define REG_PWM_LED9	0x0C
#define REG_PWM_LED10	0x0D
#define REG_PWM_LED11	0x0E
#define REG_PWM_LED12	0x0F
 
// For every pin' enable
#define	REG_EN_1	0X13 /*Bit5-Bit3 >> LED3-LED1*/
#define	REG_EN_2	0X14 /*Bit5-Bit0 >> LED9-LED4*/
#define	REG_EN_3	0X15 /*Bit2-Bit0 >> LED12-LED10*/
 
// For global control
#define	REG_UPDATE  0x16
#define	REG_CLEAR   0x17
/* End: Registers */
 
/* Start: Masks for registers */
#define	MSK_CHIP_EN	0x01
#define	MSK_CHIP_DEN	0x00
 
#define MSK_LED1_EN	(1<<3)
#define MSK_LED2_EN	(1<<4)
#define MSK_LED3_EN	(1<<5)
#define MSK_LED4_EN	(1<<0)
#define MSK_LED5_EN	(1<<1)
#define MSK_LED6_EN	(1<<2)
#define MSK_LED7_EN	(1<<3)
#define MSK_LED8_EN	(1<<4)
#define MSK_LED9_EN	(1<<5)
#define MSK_LED10_EN	(1<<0)
#define MSK_LED11_EN	(1<<1)
#define MSK_LED12_EN	(1<<2)
/* End: Masks for registers */
 
enum led_num {
	LED1	=  0,
	LED2	=  1,
	LED3	=  2,
	LED4	=  3,
	LED5	=  4,
	LED6	=  5,
	LED7	=  6,
	LED8	=  7,
	LED9	=  8,
	LED10	=  9,
	LED11	= 10,
	LED12	= 11,
	LEDALL	= 12,
};
 
enum led_ioctrl_cmd {
	SET_LED = 1,
	GET_LED = 2,
};
 
struct led_ctrl {
	unsigned char pwm_val;
	enum led_num index;
};
 
struct rgbled_data {
	unsigned char led_data[MAX_LED];
	unsigned char led_pwr_state[MAX_LED];
};
 
struct status {
	unsigned char pwm_val;
	unsigned char *enable_register;
};
 
static struct i2c_client *sn3112_i2c_client;
 
#define BUFLEN  8
const static unsigned char led_enable_register[MAX_LED] = {
	REG_EN_1,	REG_EN_1,	REG_EN_1,
	REG_EN_2,	REG_EN_2,	REG_EN_2,
	REG_EN_2,	REG_EN_2,	REG_EN_2,
	REG_EN_3,	REG_EN_3,	REG_EN_3,
};
 
const static unsigned char led_enable_mask[MAX_LED] = {
	MSK_LED1_EN,	MSK_LED2_EN,	MSK_LED3_EN,
	MSK_LED4_EN,	MSK_LED5_EN,	MSK_LED6_EN,
	MSK_LED7_EN,	MSK_LED8_EN,	MSK_LED9_EN,
	MSK_LED10_EN,	MSK_LED11_EN,	MSK_LED12_EN,
};
 
const static unsigned char pwm_value_register[MAX_LED] = {
	REG_PWM_LED1,	REG_PWM_LED2,	REG_PWM_LED3, 
	REG_PWM_LED4,	REG_PWM_LED5,	REG_PWM_LED6, 
	REG_PWM_LED7,	REG_PWM_LED8,	REG_PWM_LED9, 
	REG_PWM_LED10,	REG_PWM_LED11,	REG_PWM_LED12,
};
 
static unsigned char reg_en_a = 0, reg_en_b = 0, reg_en_c = 0;
 
struct status led_status[MAX_LED] = {
	{0, &reg_en_a}, {0, &reg_en_a}, {0, &reg_en_a}, 
	{0, &reg_en_b}, {0, &reg_en_b}, {0, &reg_en_b}, 
	{0, &reg_en_b}, {0, &reg_en_b}, {0, &reg_en_b}, 
	{0, &reg_en_c}, {0, &reg_en_c}, {0, &reg_en_c}, 
};
 
#define chip_enable() do {\
	i2c_smbus_write_byte_data(sn3112_i2c_client, REG_CHIP_ENABLE, MSK_CHIP_EN);\
} while (0)
 
#define chip_disable() do {\
	i2c_smbus_write_byte_data(sn3112_i2c_client, REG_CHIP_ENABLE, MSK_CHIP_DEN);\
} while (0)
 
#define chip_reset() do {\
	i2c_smbus_write_byte_data(sn3112_i2c_client, REG_CLEAR, MSK_CHIP_EN);\
} while (0)
 
static int set_led_status(enum led_num num, unsigned char val)
{
	int ret;
	int i;
 
	num -=1;
	if (num > LEDALL) {
		num = LEDALL;
	}
 
	if (val > 255) {
		val = 255;
	}
 
	if (num == LEDALL) {
		for (i = LED1; i < LEDALL; i++) {
			led_status[i].pwm_val = val;
			if (val == 0) {
				*led_status[i].enable_register &= ~led_enable_mask[i];
			} else {
				*led_status[i].enable_register |= led_enable_mask[i];
			}
 
			ret = i2c_smbus_write_byte_data(sn3112_i2c_client, pwm_value_register[i], led_status[i].pwm_val);
			ret = i2c_smbus_write_byte_data(sn3112_i2c_client, led_enable_register[i], *led_status[i].enable_register);
		}
	} else {
		led_status[num].pwm_val = val;
		if (val == 0) {
			*led_status[num].enable_register &= ~led_enable_mask[num];
		} else {
			*led_status[num].enable_register |= led_enable_mask[num];
		}
 
		ret = i2c_smbus_write_byte_data(sn3112_i2c_client, pwm_value_register[num], led_status[num].pwm_val);
		ret = i2c_smbus_write_byte_data(sn3112_i2c_client, led_enable_register[num], *led_status[num].enable_register);
	}
 
	ret = i2c_smbus_write_byte_data(sn3112_i2c_client, REG_UPDATE, MSK_CHIP_EN);
 
	return ret;
}
 
inline unsigned char char_to_hex(unsigned char ch)
{
	unsigned char hex = 0;
	if ((ch >= '0') && (ch <= '9'))
		hex = ch - '0';
	else if ((ch >= 'a') && (ch <= 'f'))
		hex = ch -'a' + 0xa;
	else if ((ch >= 'A') && (ch <= 'F'))
		hex = ch -'A' + 0xa;
 
	return hex;
}
 
static unsigned char string_to_hex(unsigned char l, unsigned char r)
{
	unsigned char hex;
	hex = (char_to_hex(l) <<4) + char_to_hex(r);
 
	return hex;
}
 
static int sn3112_open(struct inode *inode, struct file *file)
{
	return 0;
}
 
static int sn3112_release(struct inode *inode, struct file *file)
{
	return 0;
}
 
static long sn3112_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct led_ctrl *ctrl;
 
	ctrl = (struct led_ctrl *)arg;
 
	if (cmd == SET_LED) {
		ret = set_led_status(ctrl->index, ctrl->pwm_val);
	} else if (cmd == GET_LED) {
 
	} else {
		ret = -EINVAL;
	}
 
	return ret;
}
 
static ssize_t sn3112_read(struct file *filp, char __user *buf, size_t buf_len, loff_t *f_pos)
{
	return 0;
}
 
static ssize_t sn3112_write(struct file *filp, const char __user *dat, size_t dat_len, loff_t *f_pos)
{
	unsigned char buf[BUFLEN];
	int ret=0;
	int len,i,n;
 
	len = (BUFLEN > dat_len) ? dat_len : BUFLEN;
	if(copy_from_user(buf, (void __user *)dat, len)) {
		printk("copy fail b0:%02X,b1:%02X,l:%d.\n",buf[0],buf[1],len);
		ret = -EFAULT;
		goto write_err;
	}
	*f_pos +=dat_len;
	ret += dat_len;
 
	n = 4;
	if (len >= n) {
		for(i = 0; i < n >> 1; i++) {
			buf[i] = string_to_hex(buf[i << 1], buf[(i << 2) + 1]);
		}
 
		set_led_status(buf[0], buf[1]);
	}
write_err:
	return ret;
}
 
static const struct file_operations sn3112_fops = {
	.owner		= THIS_MODULE,
	.open		= sn3112_open,
	.release	= sn3112_release,
	.read		= sn3112_read,
	.write		= sn3112_write,
	.unlocked_ioctl	= sn3112_ioctl,
};
 
static struct miscdevice sn3112_misc_driver = {
	.minor = MISC_DYNAMIC_MINOR,
	.name  = "pwm_led",
	.fops  = &sn3112_fops,
};
 
static int __init sn3112_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret;
 
	sn3112_i2c_client = client;
 
	chip_reset();
	chip_enable();
 
	ret = misc_register(&sn3112_misc_driver);
	if (ret != 0) {
		pr_err("sn3112_probe: sn3112_rgbled_device register failed\n");
		goto misc_device_register_err;
	}
 
 
	return ret;
 
misc_device_register_err:
	return -1;
}
 
static int __exit sn3112_remove(struct i2c_client *client)
{
	struct sn3112_chip *chip = i2c_get_clientdata(client);
 
	chip_disable();
 
	misc_deregister(&sn3112_misc_driver);
	kfree(chip);
 
	return 0;
}
 
static int sn3112_suspend(struct i2c_client *client, pm_message_t mesg)
{
	chip_disable();
 
	return 0;
}
 
static int sn3112_resume(struct i2c_client *client)
{
	chip_enable();
 
	return 0;
}
 
static const struct i2c_device_id sn3112_id[] = {
	{"sn3112", 0},
	{}
};
 
MODULE_DEVICE_TABLE(i2c, sn3112_id);
 
static struct i2c_driver sn3112_driver = {
	.probe = sn3112_probe,
	.remove = sn3112_remove,
	.id_table = sn3112_id,
	.driver = {
		.name = "sn3112",
		.owner = THIS_MODULE,
	},
	.suspend	= sn3112_suspend,
	.resume		= sn3112_resume,
};
 
static int __init sn3112_init(void)
{
	int ret;
 
	ret = i2c_add_driver(&sn3112_driver);
	if (ret < 0) {
		printk(KERN_INFO "Add sn3112 i2c driver failed\n");
		return -ENODEV;
	}
 
	return ret;
}
 
static void __exit sn3112_exit(void)
{
	chip_disable();
	i2c_del_driver(&sn3112_driver);
}
 
module_init(sn3112_init);
module_exit(sn3112_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yuyaowen");
